#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Datetime: 2021/1/5 下午2:00
# @Author  : HUANG Xiong
# @Site    : 
# @File    : funcs_to_delete.py
# @Software: PyCharm 

"""
脚本说明：很早之前写的组合的函数，目前项目中已经没有用到，之后需要删除
"""
import pandas as pd

from quant_researcher.quant.datasource_fetch.fund_api import fund_nav_related
from quant_researcher.quant.datasource_fetch.index_api import index_price_related
# from quant_researcher.quant.factors.factor_database.mutual_fund import monthly_return
from quant_researcher.quant.project_tool import df_tool, assert_tool
from quant_researcher.quant.project_tool.conf import LOCAL_CONF
from quant_researcher.quant.project_tool.hammer import slim
from quant_researcher.quant.project_tool.logger.my_logger import LOG


def get_saved_portfolio(start=None, end=None, risk_level=None):
    LOG.info('寻找的MV的统计开始和结束：%s → %s, risk_level: %s', start, end, risk_level)
    condition_dict = {}
    if start:
        condition_dict.update({'start_date': start})
    if end:
        condition_dict.update({'end_date': end})
    if risk_level:
        condition_dict['risk_level'] = risk_level
    cols_spec = {'_id': 0, 'updated_on': 0}
    db = LOCAL_CONF['mongo']['db_in_use']
    table = LOCAL_CONF['portfolio']['portfolio_table']
    cursor = my_mongo.get_client()[db][table].find(condition_dict, cols_spec)
    data = list(cursor)
    if len(data) == 0:
        LOG.warning('Found no record according to %s', condition_dict)
    return data


def find_where_a_small_one_belongs(a_col, portfolio):
    LOG.info('将要寻找所属关系的小类列名：%s', a_col)
    a_type = a_col.split('_')[0]
    ucode = a_col.split('_')[1]
    for big_ones_name, small_ones in portfolio.items():
        found_it = False
        for a_small_one in small_ones['split']:
            if a_small_one['ucode'] == ucode and a_small_one['a_type'] == a_type:
                found_it = True
                break
        if found_it:
            LOG.info('这个小类所属的大类：%s', big_ones_name)
            return big_ones_name


def get_all_big(portfolio, interval, how_to_join='outer', fill_method=None):
    """
    根据大类的信息获取大类的行情，大类信息中包含了子类的代码和权重

    :param portfolio: dict，组合信息
    :param interval: tuple，行情区间，（开始，结束）
    :param how_to_join: inner OR outer，默认：outer
    :param fill_method: 如果要填充的话，用什么方法进行，默认：None
    :return: (df, weight)
    df，不同大类的行情信息，参考格式如下：
        +------------+--------------+--------------
        |  date_col  | index_000985 | index_000300
        +------------+--------------+--------------
        | 2014-04-01 |   2834.97    |   2163.115
        | 2014-04-02 |   2841.915   |   2180.727
        | 2014-04-03 |   2831.165   |   2165.008
        | 2014-04-04 |   2860.838   |   2185.472
        | 2014-04-08 |   2906.994   |   2237.316
    weight，权重字典
        如果是：index，这个是（对应上面那个index输入）：{
            '000985': {'weight': 1, 'belong': '股票', 'is_boc': False},
            'H11001': {'weight': 1, 'belong': '债券', 'is_boc': False},
            'CCFI': {'weight': 1, 'belong': '商品', 'is_boc': False},
            'H11025': {'weight': 1, 'belong': '现金', 'is_boc': False}
        }
        如果是：fund，这个是（对应上面那个fund输入）：{'000001': 0.1, '000002': 0.3}
    """
    LOG.info(f'Join方式：{how_to_join}，填充方法：{fill_method}')

    start, end = interval
    all_big = None  # 经过处理的所有的大类的单价信息
    asset_weight_dict = {}  # 每一只基金的所在对应大类中的权重
    for item in portfolio:
        LOG.info('处理大类：%s。', item)
        small_ones = portfolio[item]
        total = small_ones['total']
        split_ones = small_ones['split']
        for a_small in split_ones:
            slim(f'现在处理小类：{str(a_small)}')
            asset_type = a_small['a_type']
            ucode = a_small['ucode']
            # 不能把组合信息字典pop了，这里是引用而已
            is_boc = a_small.get('is_bond_or_currency', False)
            asset_name = asset_type + '_' + ucode
            if asset_weight_dict.__contains__(asset_name):
                raise RuntimeError('估计是小类配置重复了~~<%s>' % asset_name)
            weight = total * a_small['weight']
            asset_weight_dict.update({asset_name: {
                'weight': weight, 'belong': item, 'is_boc': is_boc
            }})

            # 根据不同的asset的类型，寻找在对应区间段内的行情
            if asset_type == 'fund':
                # df = fund_nav_related.get_fund_adj_nav(
                #     fund_code=ucode,
                #     date_related_filter=[(fund_nav_related.C_DATE_CUM_NAV, start, end)],
                #     order_by=fund_nav_related.C_DATE_CUM_NAV,
                #     return_only_nav=False
                # )
                df = fund_nav_related.get_fund_nav(
                    fund_code=ucode,
                    start_date=start,
                    end_date=end
                )
                df['end_date'] = pd.to_datetime(df['end_date'])
                df['end_date'] = df['end_date'].dt.date
                df = df.sort_values(by='end_date')
                df = df.rename(columns={
                    'end_date': 'p_date',
                    fund_nav_related.C_CUM_NAV: asset_name
                })
                del df[fund_nav_related.C_C]
            elif asset_type == 'index':
                df = index_price_related.get_hq_by_code_origin(
                    x=ucode,
                    date_related_filter=[(index_price_related.C_DATE_ORIGIN, start, end)],
                    order_by=index_price_related.C_DATE_ORIGIN,
                    return_only_price_col=False
                )
                df = df.rename(columns={
                    index_price_related.C_DATE_ORIGIN: 'p_date',
                    index_price_related.C_PRICE: asset_name
                })
                del df[index_price_related.C_C]
            else:
                df, cargo = monthly_return.fund_index_query(
                    u_code=ucode, start=start, end=end, index_or_fund=asset_type,
                    verbose=True,
                    format_date=True, is_bond_or_currency=is_boc
                )
                del df[cargo['code_col']]
                df = df.rename(columns={
                    cargo['date_col']: 'p_date', cargo['price_col']: asset_name
                })
            slim(df, name=f'{asset_type}{ucode},{start} to {end}', a_f=1)
            if all_big is None:
                all_big = df
            else:
                all_big = df_tool.merge([all_big, df], 'p_date', fill_na=False)

    # 注意：尽管在sql的时候进行了排序，但是在对DF进行join的时候，之后可能顺序不再是之前的顺序了，需要重新排序
    all_big.sort_values(by='p_date', inplace=True)
    all_big.index = pd.Index(list(range(len(all_big))), dtype='object')

    assert_tool.value_in_list(fill_method, ['mean', 'f&b'])
    if fill_method == 'mean':
        # 指数的数据，真的是，目前还是有些缺失，直觉告诉我用均值放进去会比较好
        index_mean = all_big.mean()
        mean_dict = {}
        for ii in index_mean.index:
            mean_dict.update({ii: index_mean[ii]})
        all_big.fillna(value=mean_dict, inplace=True)
    elif fill_method == 'f&b':
        # 先F，再B，顺序不能乱
        all_big.fillna(method='ffill', inplace=True)  # 用前一个观测值填充
        all_big.fillna(method='bfill', inplace=True)  # 用后一个观测值填充

    # DEBUG
    # slim(asset_weight_dict, a_f=1, name='各小类的权重及相关信息')
    # slim(all_big, show_all=True, show_index=1)

    return all_big, asset_weight_dict


def compute_portfolio_nav_and_chg(df, weight_dict, provided_last_nav_data=False, **kwargs):
    """
    根據組合的小類的行情和權重字典，計算組合的淨值變化和漲跌幅

    :param df: df，根據日期從小到大的組合中各個小類的淨值數據
    :param provided_last_nav_data: bool，组合上一期的净值，盈利，涨跌幅等信息 是不是 已经提供了
    :param weight_dict: dict，每一个资产的最终占总资产的权重
    :param kwargs:
        risk_level: 此组合对应的风险等级
        last_nav_data: dict，组合上一期的净值，盈利，涨跌幅等信息
        return_detail: bool，是不是 需要返回组合中每一个产品的详细变化情况
        share_dict: dict，组合中每一个产品的份额
    :return: 大類的按照日期從小到大的淨值和漲跌幅
    """
    risk_level = kwargs.pop('risk_level', None)
    last_nav_data = kwargs.pop('last_nav_data', None)
    return_detail = kwargs.pop('return_detail', None)
    share_dict = kwargs.pop('share_dict', None)

    head_prices = df.head(1)
    today = head_prices['p_date'].values[0]
    LOG.info('根据df的第一行显示，今天：%s', today)

    LOG.info('组合上一期的净值，盈利，涨跌幅等信息 是不是 已经提供了：%s', provided_last_nav_data)
    if not provided_last_nav_data:  # 如果在哪里都找不到上一个周期的期末信息的话，就只能使用初始配置了
        init_money = LOCAL_CONF.getfloat('other', 'init_money')
        init_nav = 1.0
    else:
        # if last_nav_data is None:  # 如果说了有，但是这个值为空，那就只能去DB找找来看
        #     # 初始资金的确定，看看数据库的 组合每日净值表 里面有没有起始日期前一天的数据，有就用那一天的总净值，没的话，就是初始资金
        #     # 这里需要保证 组合每日净值表 里面每一个自然都是有组合净值的
        #     last_day = date_shifter(before=today, step='days', how_many=-1, fmt_str=DATE_FMT)
        #     LOG.info('那么去尝试寻找净值的日期：%s，风险等级：%s', last_day, risk_level)
        #     cursor = my_mongo.get_client()[PORT_HQ_DB][PORT_HQ_TABLE].find(
        #         {'changedate': last_day, 'risk_level': risk_level}, {'_id': 0, 'updated_on': 0}
        #     )
        #     last_nav_data = list(cursor)
        #     data_len = len(last_nav_data)
        #     if data_len > 1:
        #         err_msg = '在组合净值行情中找到多个记录：changedate=%s, risk_level=%s' % (last_day, risk_level)
        #         LOG.error(err_msg)
        #         raise RuntimeError(err_msg)
        #     last_nav_data = last_nav_data[0]
        LOG.info('Nav info last day: %s', last_nav_data)
        init_money = last_nav_data['float_p']
        init_nav = last_nav_data['p_nav']
    LOG.info('那么这一期的初始资金：%s，初始淨值：%s', init_money, init_nav)

    LOG.info('获得每一个资产（如：基金产品）的份额 或者 使用传进来的')
    provided_share_dict = True  # 组合中各个小类资产的份额字典是自己计算的还是从其他的获得的
    if share_dict is None:
        share_dict = get_share_dict(weight_dict, init_money, head_prices)
        provided_share_dict = False

    # DEBUG
    # slim(share_dict, a_f=1)

    LOG.info('已经获得所有资产的行情数据')
    LOG.info('接下来，根据份额持有字典（根据期初的价格及权重计算所得）计算每天的浮盈')
    share_dict_bak = share_dict.copy()
    # 计算完之后，各个小类的份额，如果其中含有类似债券或货币型基金的话，该小类的份额也会变化
    # slim(df, show_index=True, show_all=True, a_f=1, name='原始行情')
    portfolio_hq_df = compute_portfolio_float_profit(share_dict, weight_dict, df, provided_share_dict)
    LOG.info('计算完之后，df已经变成了float_profit_of_each_asset_df，即各类资产的每日浮盈')

    # 如果是最开始的时候，手动将首日的净值置为初始资金
    if not provided_share_dict and init_money != portfolio_hq_df.values[0][0]:
        LOG.info(f'以为的期初资金：{init_money}，实际计算出来的组合的期初资金：{portfolio_hq_df.values[0][0]}')
        slim(share_dict_bak, a_f=1, name='份额字典（备份）')
        slim(share_dict, a_f=1, name='份额字典')
        slim(weight_dict, a_f=1, name='权重字典')
        slim(df, show_index=True, show_all=True, a_f=1)
        slim(portfolio_hq_df, show_index=True)

    LOG.info('增加：组合每日涨跌幅并把第一天的涨跌幅设置为0')
    portfolio_hq_df['chg'] = portfolio_hq_df.pct_change()
    portfolio_hq_df.fillna(0.0, inplace=True)

    LOG.info('增加“p_date”列，并把它放到第一列的位置，不能放在之前，那样pct_change就会尴尬')
    portfolio_hq_df.insert(0, 'p_date', df.p_date)

    LOG.info('新的淨值算法，根據組合的浮盈來計算，计算组合的“拟合净值”')
    compute_fit_nav_for_portfolio(portfolio_hq_df, init_nav)

    # DEBUG
    slim(portfolio_hq_df, show_index=1, a_f=1)
    slim(df, a_f=1)
    # slim(share_dict, a_f=0)

    if return_detail:
        # 分别是：组合浮盈数据，各类资产浮盈数据，各类资产份额字典
        return portfolio_hq_df, df, share_dict
    else:
        # 组合浮盈数据
        return portfolio_hq_df


def compute_portfolio_float_profit(share_dict, weight_dict, df, compute_the_1st_rtn):
    """
    计算组合的汇总浮盈

    :param share_dict: dict，各个小类的份额字典
    :param weight_dict: dict，各个小类的权重字典
    :param df: DF，组合中各个小类资产的行情
    :param compute_the_1st_rtn: bool，是不是跳过第一天的行情收益，如果是债券或者货币型基金的时候
    :return: DF，组合汇总之后的浮盈DF
    """
    for asset_name, asset_share in share_dict.items():
        new_col = 'p_%s' % asset_name
        if weight_dict[asset_name]['is_boc']:
            v_list = df[asset_name].values  # 货币型或者债券型基金每天的行情
            new_v_list = []
            last_v = asset_share
            for jj in range(len(v_list)):
                if compute_the_1st_rtn or jj != 0:  # 投资当天不计算收益
                    last_v += (last_v * v_list[jj] / 10000)  # 对于万份收益的基金，算出每天累计收益
                new_v_list.append(last_v)
            share_dict.update({asset_name: last_v})  # 因为债券型或者货币型基金，随着时间，份额是在不断变化的，这里需要更新下
            df[new_col] = new_v_list
        else:
            df[new_col] = df[asset_name] * asset_share
        del df[asset_name]  # 每日资产的行情变化不再需要
        df.rename(columns={new_col: asset_name}, inplace=True)  # 使用此资产的浮盈代替原来的行情
    LOG.info('看看DF的列有哪些？{%s}', df.columns.values)
    assert df.columns.values[0] == 'p_date'  # 保证第一列一定是日期列，因为后面要使用这个关系来计算当天组合浮盈
    LOG.info('组合的个类资产的每日浮盈变化计算完成，既然对DF做了处理，不用显式返回')

    LOG.info('接下来，计算组合每天浮盈')
    asset_count = len(weight_dict)
    LOG.info('资产个数：%s', asset_count)
    tt_serials = df.apply(lambda x: sum(x.values[-1 * asset_count:]), axis=1)
    portfolio_hq_df = pd.DataFrame({'float_p': tt_serials.values})  # 组合每日浮盈

    # DEBUG
    # slim(df, show_index=True, a_f=1)
    # slim(portfolio_hq_df, show_index=True)

    return portfolio_hq_df


def compute_fit_nav_for_portfolio(portfolio_hq_df, init_nav):
    old_float_p_list = portfolio_hq_df['float_p'].values
    new_float_p_list = []
    last_day_nav = 1.0
    for idx in range(len(old_float_p_list)):
        if idx == 0:
            this_day_nav = init_nav
        else:
            this_day_nav = old_float_p_list[idx] / old_float_p_list[idx - 1] * last_day_nav
        last_day_nav = this_day_nav
        new_float_p_list.append(this_day_nav)
    LOG.info('增加“拟合净值”（p_nav）列到原来的DF中，讲道理，这里传递的是引用，应该不用显式返回')
    portfolio_hq_df['p_nav'] = new_float_p_list


def write_portfolio_hq_to_db_func(df, risk_level, asset_weight, fund_weight):
    """
    将投资组合的行情数据写到对应的库中

    :param df: 表示基础行情的df
    :param risk_level: 此组合对应的风险等级
    :param asset_weight: dict，大类配置的权重
    :param fund_weight: dict，小类在各自大类中配置的权重
    :return:
    """
    LOG.info('当前组合的风险等级：%s。', risk_level)

    df = compute_portfolio_nav_and_chg(df, risk_level, asset_weight, fund_weight)

    df['risk_level'] = risk_level
    updated_on = time_tool.get_updated_on()
    df['updated_on'] = updated_on

    # assert False
    my_mongo.write_df(df, PORT_HQ_DB, PORT_HQ_TABLE, p_keys=['changedate', 'risk_level'])
    LOG.info('成功写入这个风险等级对应的原来的组合的行情数据（更新时间：%s）', updated_on)


def get_share_dict(weight_dict, init_money, head_prices):
    LOG.info('是没有传进来share字典的，那么根据已有信息进行计算了')
    share_dict = {}
    for k, v in weight_dict.items():
        this_money = init_money * v['weight']  # 这里的权重是各个小类占组合整体的比例
        this_price = head_prices[k].values[0]
        if v['is_boc']:
            this_share = this_money  # 货币基金或者债券基金，份额直接是投入的金额
        else:
            this_share = this_money / this_price
        share_dict.update({k: this_share})
    return share_dict